ApiPageSet may contain entries where the 'from' value is percent-encoded as
the raw value cannot be represented in a valid API response. These are
indicated by a 'fromencoded' boolean alongside the existing 'from' parameter.
+* (T28680) action=paraminfo can now return info about all submodules of a
+ module without listing them all explicitly.
=== Action API internal changes in 1.28 ===
* Added a new hook, 'ApiMakeParserOptions', to allow extensions to better
$page = Title::makeTitle( NS_USER, $userName );
}
+ // Wrap the output with <bdi> tags for directionality isolation
return self::link(
$page,
- htmlspecialchars( $altUserName !== false ? $altUserName : $userName ),
+ '<bdi>' . htmlspecialchars( $altUserName !== false ? $altUserName : $userName ) . '</bdi>',
[ 'class' => $classes ]
);
}
$this->context->setLanguage( $this->getMain()->getLanguage() );
if ( is_array( $params['modules'] ) ) {
- $modules = $params['modules'];
+ $modules = [];
+ foreach ( $params['modules'] as $path ) {
+ if ( $path === '*' || $path === '**' ) {
+ $path = "main+$path";
+ }
+ if ( substr( $path, -2 ) === '+*' || substr( $path, -2 ) === ' *' ) {
+ $submodules = true;
+ $path = substr( $path, 0, -2 );
+ $recursive = false;
+ } elseif ( substr( $path, -3 ) === '+**' || substr( $path, -3 ) === ' **' ) {
+ $submodules = true;
+ $path = substr( $path, 0, -3 );
+ $recursive = true;
+ } else {
+ $submodules = false;
+ }
+
+ if ( $submodules ) {
+ try {
+ $module = $this->getModuleFromPath( $path );
+ } catch ( UsageException $ex ) {
+ $this->setWarning( $ex->getMessage() );
+ }
+ $submodules = $this->listAllSubmodules( $module, $recursive );
+ if ( $submodules ) {
+ $modules = array_merge( $modules, $submodules );
+ } else {
+ $this->setWarning( "Module $path has no submodules" );
+ }
+ } else {
+ $modules[] = $path;
+ }
+ }
} else {
$modules = [];
}
$formatModules = [];
}
+ $modules = array_unique( $modules );
+
$res = [];
foreach ( $modules as $m ) {
$result->addValue( null, $this->getModuleName(), $res );
}
+ /**
+ * List all submodules of a module
+ * @param ApiBase $module
+ * @param boolean $recursive
+ * @return string[]
+ */
+ private function listAllSubmodules( ApiBase $module, $recursive ) {
+ $manager = $module->getModuleManager();
+ if ( $manager ) {
+ $paths = [];
+ $names = $manager->getNames();
+ sort( $names );
+ foreach ( $names as $name ) {
+ $submodule = $manager->getModule( $name );
+ $paths[] = $submodule->getModulePath();
+ if ( $recursive && $submodule->getModuleManager() ) {
+ $paths = array_merge( $paths, $this->listAllSubmodules( $submodule, $recursive ) );
+ }
+ }
+ }
+ return $paths;
+ }
+
/**
* @param array $res Result array
* @param string $key Result key
protected function getExamplesMessages() {
return [
- 'action=paraminfo&modules=parse|phpfm|query+allpages|query+siteinfo'
+ 'action=paraminfo&modules=parse|phpfm|query%2Ballpages|query%2Bsiteinfo'
=> 'apihelp-paraminfo-example-1',
+ 'action=paraminfo&modules=query%2B*'
+ => 'apihelp-paraminfo-example-2',
];
}
"apihelp-options-example-complex": "Reset all preferences, then set <kbd>skin</kbd> and <kbd>nickname</kbd>.",
"apihelp-paraminfo-description": "Obtain information about API modules.",
- "apihelp-paraminfo-param-modules": "List of module names (values of the <var>action</var> and <var>format</var> parameters, or <kbd>main</kbd>). Can specify submodules with a <kbd>+</kbd>.",
+ "apihelp-paraminfo-param-modules": "List of module names (values of the <var>action</var> and <var>format</var> parameters, or <kbd>main</kbd>). Can specify submodules with a <kbd>+</kbd>, or all submodules with <kbd>+*</kbd>, or all submodules recursively with <kbd>+**</kbd>.",
"apihelp-paraminfo-param-helpformat": "Format of help strings.",
"apihelp-paraminfo-param-querymodules": "List of query module names (value of <var>prop</var>, <var>meta</var> or <var>list</var> parameter). Use <kbd>$1modules=query+foo</kbd> instead of <kbd>$1querymodules=foo</kbd>.",
"apihelp-paraminfo-param-mainmodule": "Get information about the main (top-level) module as well. Use <kbd>$1modules=main</kbd> instead.",
"apihelp-paraminfo-param-pagesetmodule": "Get information about the pageset module (providing titles= and friends) as well.",
"apihelp-paraminfo-param-formatmodules": "List of format module names (value of <var>format</var> parameter). Use <var>$1modules</var> instead.",
"apihelp-paraminfo-example-1": "Show info for <kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>, <kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>, <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>, and <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>.",
+ "apihelp-paraminfo-example-2": "Show info for all submodules of <kbd>[[Special:ApiHelp/query|action=query]]</kbd>.",
"apihelp-parse-description": "Parses content and returns parser output.\n\nSee the various prop-modules of <kbd>[[Special:ApiHelp/query|action=query]]</kbd> to get information from the current version of a page.\n\nThere are several ways to specify the text to parse:\n# Specify a page or revision, using <var>$1page</var>, <var>$1pageid</var>, or <var>$1oldid</var>.\n# Specify content explicitly, using <var>$1text</var>, <var>$1title</var>, and <var>$1contentmodel</var>.\n# Specify only a summary to parse. <var>$1prop</var> should be given an empty value.",
"apihelp-parse-param-title": "Title of page the text belongs to. If omitted, <var>$1contentmodel</var> must be specified, and [[API]] will be used as the title.",
"apihelp-paraminfo-param-pagesetmodule": "{{doc-apihelp-param|paraminfo|pagesetmodule}}",
"apihelp-paraminfo-param-formatmodules": "{{doc-apihelp-param|paraminfo|formatmodules}}",
"apihelp-paraminfo-example-1": "{{doc-apihelp-example|paraminfo}}",
+ "apihelp-paraminfo-example-2": "{{doc-apihelp-example|paraminfo}}",
"apihelp-parse-description": "{{doc-apihelp-description|parse}}",
"apihelp-parse-param-title": "{{doc-apihelp-param|parse|title}}",
"apihelp-parse-param-text": "{{doc-apihelp-param|parse|text}}",
* a transaction will automatically be wrapped around the update. Starting another
* one would break the outer transaction bracket. If need be, subclasses can override
* the beginTransaction() and commitTransaction() methods.
+ *
+ * @deprecated Since 1.28 Use DataUpdate directly, injecting the database
*/
abstract class SqlDataUpdate extends DataUpdate {
/** @var IDatabase Database connection reference */
use \MediaWiki\Logger\LoggerFactory;
-/**
- * Bump this number when serialized cache records may be incompatible.
- */
-define( 'MW_FILE_VERSION', 9 );
-
/**
* Class to represent a local file in the wiki's own database
*
* @ingroup FileAbstraction
*/
class LocalFile extends File {
+ const VERSION = 10; // cache version
+
const CACHE_FIELD_MAX_LEN = 1000;
/** @var bool Does the file exist on disk? (loadFromXxx) */
* @return string|bool
*/
function getCacheKey() {
- $hashedName = md5( $this->getName() );
-
- return $this->repo->getSharedCacheKey( 'file', $hashedName );
+ return $this->repo->getSharedCacheKey( 'file', sha1( $this->getName() ) );
}
/**
- * Try to load file metadata from memcached. Returns true on success.
- * @return bool
+ * Try to load file metadata from memcached, falling back to the database
*/
private function loadFromCache() {
$this->dataLoaded = false;
$this->extraDataLoaded = false;
- $key = $this->getCacheKey();
+ $key = $this->getCacheKey();
if ( !$key ) {
- return false;
- }
-
- $cache = ObjectCache::getMainWANInstance();
- $cachedValues = $cache->get( $key );
+ $this->loadFromDB( self::READ_NORMAL );
- // Check if the key existed and belongs to this version of MediaWiki
- if ( is_array( $cachedValues ) && $cachedValues['version'] == MW_FILE_VERSION ) {
- $this->fileExists = $cachedValues['fileExists'];
- if ( $this->fileExists ) {
- $this->setProps( $cachedValues );
- }
- $this->dataLoaded = true;
- $this->extraDataLoaded = true;
- foreach ( $this->getLazyCacheFields( '' ) as $field ) {
- $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
- }
+ return;
}
- return $this->dataLoaded;
- }
-
- /**
- * Save the file metadata to memcached
- * @param array $cacheSetOpts Result of Database::getCacheSetOptions()
- */
- private function saveToCache( array $cacheSetOpts ) {
- $this->load();
+ $cache = ObjectCache::getMainWANInstance();
+ $cachedValues = $cache->getWithSetCallback(
+ $key,
+ $cache::TTL_WEEK,
+ function ( $oldValue, &$ttl, array &$setOpts ) use ( $cache ) {
+ $setOpts += Database::getCacheSetOptions( $this->repo->getSlaveDB() );
+
+ $this->loadFromDB( self::READ_NORMAL );
+
+ $fields = $this->getCacheFields( '' );
+ $cacheVal['fileExists'] = $this->fileExists;
+ if ( $this->fileExists ) {
+ foreach ( $fields as $field ) {
+ $cacheVal[$field] = $this->$field;
+ }
+ }
+ // Strip off excessive entries from the subset of fields that can become large.
+ // If the cache value gets to large it will not fit in memcached and nothing will
+ // get cached at all, causing master queries for any file access.
+ foreach ( $this->getLazyCacheFields( '' ) as $field ) {
+ if ( isset( $cacheVal[$field] )
+ && strlen( $cacheVal[$field] ) > 100 * 1024
+ ) {
+ unset( $cacheVal[$field] ); // don't let the value get too big
+ }
+ }
- $key = $this->getCacheKey();
- if ( !$key ) {
- return;
- }
+ if ( $this->fileExists ) {
+ $ttl = $cache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
+ } else {
+ $ttl = $cache::TTL_DAY;
+ }
- $fields = $this->getCacheFields( '' );
- $cacheVal = [ 'version' => MW_FILE_VERSION ];
- $cacheVal['fileExists'] = $this->fileExists;
+ return $cacheVal;
+ },
+ [ 'version' => self::VERSION ]
+ );
+ $this->fileExists = $cachedValues['fileExists'];
if ( $this->fileExists ) {
- foreach ( $fields as $field ) {
- $cacheVal[$field] = $this->$field;
- }
+ $this->setProps( $cachedValues );
}
- // Strip off excessive entries from the subset of fields that can become large.
- // If the cache value gets to large it will not fit in memcached and nothing will
- // get cached at all, causing master queries for any file access.
+ $this->dataLoaded = true;
+ $this->extraDataLoaded = true;
foreach ( $this->getLazyCacheFields( '' ) as $field ) {
- if ( isset( $cacheVal[$field] ) && strlen( $cacheVal[$field] ) > 100 * 1024 ) {
- unset( $cacheVal[$field] ); // don't let the value get too big
- }
+ $this->extraDataLoaded = $this->extraDataLoaded && isset( $cachedValues[$field] );
}
-
- // Cache presence for 1 week and negatives for 1 day
- $wanCache = ObjectCache::getMainWANInstance();
- if ( $this->fileExists ) {
- $ttl = $wanCache::TTL_WEEK;
- $ttl = $wanCache->adaptiveTTL( wfTimestamp( TS_UNIX, $this->timestamp ), $ttl );
- } else {
- $ttl = $wanCache::TTL_DAY;
- }
- $wanCache->set( $key, $cacheVal, $ttl, $cacheSetOpts );
}
/**
*/
function load( $flags = 0 ) {
if ( !$this->dataLoaded ) {
- if ( ( $flags & self::READ_LATEST ) || !$this->loadFromCache() ) {
- $opts = Database::getCacheSetOptions( $this->repo->getSlaveDB() );
+ if ( $flags & self::READ_LATEST ) {
$this->loadFromDB( $flags );
- $this->saveToCache( $opts );
+ } else {
+ $this->loadFromCache();
}
- $this->dataLoaded = true;
}
+
if ( ( $flags & self::LOAD_ALL ) && !$this->extraDataLoaded ) {
// @note: loads on name/timestamp to reduce race condition problems
$this->loadExtraFromDB();
if ( $type == 'text' ) {
return $this->user_text;
- } elseif ( $type == 'id' ) {
+ } else { // id
return (int)$this->user;
}
}
$sha1 = $repo->isVirtualUrl( $srcPath )
? $repo->getFileSha1( $srcPath )
: FSFile::getSha1Base36FromPath( $srcPath );
- $dst = $repo->getBackend()->getPathForSHA1( $sha1 );
+ /** @var FileBackendDBRepoWrapper $wrapperBackend */
+ $wrapperBackend = $repo->getBackend();
+ $dst = $wrapperBackend->getPathForSHA1( $sha1 );
$status = $repo->quickImport( $src, $dst );
if ( $flags & File::DELETE_SOURCE ) {
unlink( $srcPath );
* @return FileRepoStatus
*/
public function execute() {
+ /** @var Language */
global $wgLang;
$repo = $this->file->getRepo();
[
'<a href="/wiki/Special:Contributions/JohnDoe" '
. 'class="mw-userlink mw-anonuserlink" '
- . 'title="Special:Contributions/JohnDoe">JohnDoe</a>',
+ . 'title="Special:Contributions/JohnDoe"><bdi>JohnDoe</bdi></a>',
0, 'JohnDoe', false,
],
[
'<a href="/wiki/Special:Contributions/::1" '
. 'class="mw-userlink mw-anonuserlink" '
- . 'title="Special:Contributions/::1">::1</a>',
+ . 'title="Special:Contributions/::1"><bdi>::1</bdi></a>',
0, '::1', false,
'Anonymous with pretty IPv6'
],
[
'<a href="/wiki/Special:Contributions/0:0:0:0:0:0:0:1" '
. 'class="mw-userlink mw-anonuserlink" '
- . 'title="Special:Contributions/0:0:0:0:0:0:0:1">::1</a>',
+ . 'title="Special:Contributions/0:0:0:0:0:0:0:1"><bdi>::1</bdi></a>',
0, '0:0:0:0:0:0:0:1', false,
'Anonymous with almost pretty IPv6'
],
[
'<a href="/wiki/Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001" '
. 'class="mw-userlink mw-anonuserlink" '
- . 'title="Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001">::1</a>',
+ . 'title="Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001"><bdi>::1</bdi></a>',
0, '0000:0000:0000:0000:0000:0000:0000:0001', false,
'Anonymous with full IPv6'
],
[
'<a href="/wiki/Special:Contributions/::1" '
. 'class="mw-userlink mw-anonuserlink" '
- . 'title="Special:Contributions/::1">AlternativeUsername</a>',
+ . 'title="Special:Contributions/::1"><bdi>AlternativeUsername</bdi></a>',
0, '::1', 'AlternativeUsername',
'Anonymous with pretty IPv6 and an alternative username'
],
[
'<a href="/wiki/Special:Contributions/127.0.0.1" '
. 'class="mw-userlink mw-anonuserlink" '
- . 'title="Special:Contributions/127.0.0.1">127.0.0.1</a>',
+ . 'title="Special:Contributions/127.0.0.1"><bdi>127.0.0.1</bdi></a>',
0, '127.0.0.1', false,
'Anonymous with IPv4'
],
[
'<a href="/wiki/Special:Contributions/127.0.0.1" '
. 'class="mw-userlink mw-anonuserlink" '
- . 'title="Special:Contributions/127.0.0.1">AlternativeUsername</a>',
+ . 'title="Special:Contributions/127.0.0.1"><bdi>AlternativeUsername</bdi></a>',
0, '127.0.0.1', 'AlternativeUsername',
'Anonymous with IPv4 and an alternative username'
],
private static function removeSomeHtml( $html ) {
$html = str_replace( '"', '"', $html );
$html = preg_replace( '/\xE2\x80[\x8E\x8F]/', '', $html ); // Strip lrm/rlm
- return trim( preg_replace( '/<(a|span)[^>]*>([^<]*)<\/\1>/', '$2', $html ) );
+ return trim( strip_tags( $html ) );
}
private static function removeApiMetaData( $val ) {